package middleware

import (
	"errors"
	"github.com/APP/internal/service/system_service"
	"github.com/APP/pkg/response"
	"strconv"
	"strings"
	"time"

	"github.com/APP/global"
	"github.com/APP/internal/models"
	"github.com/APP/internal/utils"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"go.uber.org/zap"
)

var jwtService = system_service.JwtService{}

// JWTAuth 是一个 Gin 的中间件函数，用于对请求进行 JWT 鉴权
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 从请求头中获取 token，通常在用户登录后前端会将 token 存储在 cookie 或 localStorage 中
		// 后端与前端需协商 token 的过期时间和刷新策略
		token := c.Request.Header.Get("x-token") // 优先从自定义头 x-token 中获取
		if token == "" {
			token = c.Request.Header.Get("token") // 其次尝试从 token 字段中获取
		}
		if token == "" {
			// 最后尝试从 Authorization 头中获取 Bearer token
			authHeader := c.Request.Header.Get("Authorization")
			if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
				// 去掉 "Bearer " 前缀，提取实际 token
				token = strings.TrimPrefix(authHeader, "Bearer ")
			}
		}
		if token == "" {
			// 如果上述方式都没有拿到 token，说明未登录或非法访问，拒绝请求
			code.Respond(c, code.TokenInvalid.WithError(errors.New("未登录或非法访问")), gin.H{"reload": true})
			c.Abort()
			return
		}

		// 判断该 token 是否已被加入黑名单（比如用户被强制登出或 token 被吊销）
		if jwtService.IsBlacklist(token) {
			code.Respond(c, code.TokenInvalid.WithError(errors.New("您的帐户异地登陆或令牌失效")), gin.H{"reload": true})
			c.Abort()
			return
		}

		// 创建一个 JWT 工具对象，用于解析 token
		j := utils.NewJWT()

		// 尝试解析 token，获取其中的 claims（声明信息）
		claims, err := j.ParseToken(token)
		if err != nil {
			// 如果 token 已过期，返回对应错误
			if errors.Is(err, utils.TokenExpired) {
				code.Respond(c, code.TokenInvalid.WithError(errors.New("授权已过期")), gin.H{"reload": true})
				c.Abort()
				return
			}
			// 其他解析错误，如签名不正确
			code.Respond(c, code.TokenInvalid, gin.H{"reload": true})
			c.Abort()
			return
		}

		// 以下是一些可选逻辑：检查用户是否被禁用或删除，这些检查可能涉及数据库查询
		// 会影响性能，默认注释掉了，需根据项目实际情况决定是否开启

		/*
			if user, err := userService.FindUserByUuid(claims.UUID); err != nil || user.Enable == 2 {
				// 将该 token 加入黑名单
				_ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token})
				// 返回错误信息
				ResponseWithDetailed(gin.H{"reload": true}, err.Error(), c)
				c.Abort()
				return
			}
		*/

		// 判断当前 token 是否接近过期（小于缓冲时间）
		if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {
			// 从配置中获取 buffer 时间（续期时间）
			dr, _ := utils.ParseDuration(global.APP_CONFIG.JWT.BufferTime)

			// 重新设置 token 的过期时间（续期）
			claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(dr))

			// 基于旧 token 生成一个新 token
			newToken, _ := j.CreateTokenByOldToken(token, *claims)

			// 解析新 token 的 claims
			newClaims, _ := j.ParseToken(newToken)

			// 将新 token 和其过期时间写入响应头，供前端使用
			c.Header("new-token", newToken)
			c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))

			// 如果启用了多点登录控制（即同一账号可在多个地方登录）
			if global.APP_CONFIG.System.UseMultipoint {
				// 获取 Redis 中记录的旧 token
				RedisJwtToken, err := jwtService.GetRedisJWT(newClaims.Username)
				if err != nil {
					// 如果获取失败，记录错误日志
					global.APP_LOG.Error("get redis jwt failed", zap.Error(err))
				} else {
					// 成功获取旧 token 后，将其加入黑名单，确保只允许新 token 有效
					_ = jwtService.JsonInBlacklist(models.JwtBlacklist{Jwt: RedisJwtToken})
				}
				// 无论是否获取旧 token 成功，都要记录新的 token 到 Redis 中
				_ = jwtService.SetRedisJWT(newToken, newClaims.Username)
			}
		}

		// 将 claims 存入上下文，供后续中间件或处理函数使用
		c.Set("claims", claims)

		// 继续执行后续处理流程
		c.Next()
	}
}
